#ifndef INSTR_HPP__
#define INSTR_HPP__

/**
 * \file instr.hpp
 * \author Mason Smith <masonium@caltech.edu>
 * Contains all of the non-arithmetic VM instructions 
 */

#include <string>
#include <vector>
#include <functional>
#include <iostream>
#include <boost/shared_ptr.hpp>
#include "value.hpp"
#include "exception.hpp"

using boost::shared_ptr;
using std::string;

namespace BMD
{
  class Actor;
  class VM;
  
  //! an instruction is merely a functor
  typedef void (* InstructionFunc)(VM*);

  /**
   * Instruction.
   * base instruction class that all operations must inherit from
   */
  class Instruction 
  {
  public:
    virtual void execute(VM* vm) = 0;
    
    virtual bool is_return() const { return false; }
    
    int get_line_number() const { return line_number; }
    void set_line_number(int _line_number)
    { line_number = _line_number; }  

    virtual ~Instruction() { }    

  private:
    int line_number;
    int op_num;
  };

  /**
   * Some instructions take optional arguments. Internally, they are sometimes 
   * represented with different classes. Instructions where the *opcode* 
   * doesn't not take an argument is denoted by a _NP prefix after the name. 
   * For instance, the class getelem_NP_instr corresponds to the opcode that 
   * takes no arguments.
   */


  /**
   * const Instruction.
   * loads a constant value into the accumulator
   */
  class const_instr : public Instruction
  {
  public:
    const_instr() { }
    const_instr(Value cvr) : v(cvr) { }
    void execute(VM* vm);
  private:
    Value v;
  };

  class pop_instr : public Instruction
  {
  public:
    pop_instr(int _n = 1) : num_elements(_n) { }
    void execute(VM* vm);
  private:
    int num_elements;
  };

  /**
   * push_sf instruction.
   * pushes the stack frame
   */
  class push_sf_instr : public Instruction
  {
  public:
    push_sf_instr(int _ret_addr) : ret_addr(_ret_addr) { }
    void execute(VM* vm);
  private:
    int ret_addr;
  };

  /**
   * assign instruction.
   * sets a certain stack position to the accumulator
   */
  class assign_instr : public Instruction
  {
  public:
    assign_instr(int _pos) : pos(_pos) { }
    void execute(VM* vm);
  private:
    int pos;
  };

  /**
   * acc instruction.
   * Load a stack element into the accumulator
   */
  class acc_instr : public Instruction
  {
  public:
    acc_instr(int _pos) : pos(_pos) { }
    void execute(VM* vm);
  private:
    int pos;
  };

  /**
   * rev_instr
   * Reverses the top [n] elements of the stack
   */
  class rev_instr : public Instruction
  {
  public:
    rev_instr(int num_to_rev) : rev_num(num_to_rev) { }
    void execute(VM* vm);
  private:
    int rev_num;
  };

  /*************************************
   * actor instructions
   ************************************/

  /**
   * getfield instruction.
   * sets the accumulator to the specified actor field
   */
  class get_field_instr : public Instruction
  {
  public:
    get_field_instr(int field_index) : index(field_index) { }
    void execute(VM* vm);
  private:
    int index;
  };
  
  /**
   * setfield instruction.
   * sets the accumulator to the specified actor field
   */
  class set_field_instr : public Instruction
  {
  public:
    set_field_instr(int field_index) : index(field_index) { }
    void execute(VM* vm);
  private:
    int index;
  };

  /*************************************
   * block instructions
   ************************************/
  /**
   * make_block instruction.
   * Creates a block with a particular runtime tag
   */
  class make_block_instr : public Instruction
  {
  public:
    make_block_instr(Block::Tag t) : tag(t) { }
    void execute(VM* vm);
  private:
    Block::Tag tag;
  };

  /** 
   * makes a block with [n] copies of some element
   */
  class make_filled_block_instr : public Instruction
  {
  public:
    make_filled_block_instr(Block::Tag t) : tag(t) { }
    void execute(VM* vm);
  private:
    Block::Tag tag;
  };

  /**
   * make_block_static instruction.
   * Creates a block with a particular runtime tag, initialized
   * with elements from the stack
   */
  class make_block_static_instr : public Instruction
  {
  public:
    make_block_static_instr(Block::Tag t, unsigned int _size) : tag(t), size(_size) { }
    void execute(VM* vm);
  private:
    Block::Tag tag;
    unsigned int size;
  };

  /**
   * getelem instruction.
   * grabs a value from a block at a predetermined index
   */
  class getelem_instr : public Instruction
  {
  public:
    getelem_instr(unsigned int _idx) : index(_idx) { }
    void execute(VM* vm);
  private:
    unsigned int index;
  };
  
  /**
   * setelem instruction.
   * set a value in a block at a predetermined index
   */
  class setelem_instr : public Instruction
  {
  public:
    setelem_instr(unsigned int _idx) : index(_idx) { }
    void execute(VM* vm);
  private:
    unsigned int index;
  };

  /*************************************
   * jump instructions
   ************************************/
  /**
   * call instruction.
   * Calls a function after the stack frame has been set up
   */
  class call_instr : public Instruction
  {
  public:
    call_instr(string name, int _func_loc) : func_loc(_func_loc), func_name(name) { }
    void execute(VM* vm);
  private:
    int func_loc;
    string func_name;
  };

  /**
   * apply instruction.
   * Applies a function, whose name is in the accumulator, to the arguments on
   * the stack. The stack must look like the stack would on a normal call.
   */
  class apply_instr : public Instruction
  {
  public:
    apply_instr() { }
    void execute(VM* vm);
  };

  /**
   * jump instruction
   * Jump unconditionally to a particularly location
   */
  class jmp_instr : public Instruction
  {
  public:
    jmp_instr(int _loc) : loc(_loc) { }
    void execute(VM* vm);
  private:
    int loc;
  };

  /**
   * jump-if-zero instruction
   * Jump to a particularly location if the accumulator is zero
   */
  class jz_instr : public Instruction
  {
  public:
    jz_instr(int _loc) : loc(_loc) { }
    void execute(VM* vm);
  private:
    int loc;
  };

  /**
   * jump-if-not-zero instruction
   * Jump to a particularly location if the accumulator is zero
   */
  class jnz_instr : public Instruction
  {
  public:
    jnz_instr(int _loc) : loc(_loc) { }
    void execute(VM* vm);
  private:
    int loc;
  };  

  /** 
   * rpc instruction
   * performs a remote procedure call 
   */
  class rpc_instr : public Instruction
  {
  public:
    rpc_instr(string _func_name) : func_name(_func_name) { }
    void execute(VM* vm);
  private:
    // helper functions for calling the vm
    bool handle_vm_call(VM* vm, Value& ret);
    bool handle_actor_call(VM* vm, Value& ret);
    string func_name;
  };

  /*************************************
   * debugging opcodes
   ************************************/
  
  /**
   * dumping instruction
   * prints a stack dump of the vm
   */
  class stack_dump_instr : public Instruction
  {
  public:
    stack_dump_instr(string _title, int _num = 0) : 
      title(_title), num_elements(_num) { }
    void execute(VM* vm);
  private:
    string title;
    int num_elements;
  };

  /**
   * instruction-building instruction, useful for state-less instructions.
   * Creates an object which just calls the specified function
   */
  template <InstructionFunc f>
  class InstructionNoParam : public Instruction
  {
  public:
    void execute(VM* vm)
    {
      f(vm);
    }
  };


/**
 * creates an instruction with name instr_name based on a function
 * func_name with header:
 * void func_name(VM* vm);
 */
#define MAKE_STATELESS_INSTR(func_name, instr_name)     \
  void func_name(VM* vm);                               \
  typedef InstructionNoParam<func_name> instr_name

  // stack operations
  MAKE_STATELESS_INSTR(push_func, push_instr);
  MAKE_STATELESS_INSTR(swap_func, swap_instr);

  // input/output operations
  MAKE_STATELESS_INSTR(read_func, read_instr);
  MAKE_STATELESS_INSTR(print_func, print_instr);
  MAKE_STATELESS_INSTR(println_func, println_instr);

  // VM state ops
  MAKE_STATELESS_INSTR(stop_func, stop_instr);
  MAKE_STATELESS_INSTR(return_func, return_temp_instr);

  class return_instr : public return_temp_instr
  {
  public:
    bool is_return() const { return true; }
  };

  // list ops
  MAKE_STATELESS_INSTR(cons_func, cons_instr);
  MAKE_STATELESS_INSTR(car_func, car_instr);
  MAKE_STATELESS_INSTR(cdr_func, cdr_instr);
  MAKE_STATELESS_INSTR(nil_func, nil_instr);

  MAKE_STATELESS_INSTR(isnull_func, isnull_instr);

  // list/string opp
  MAKE_STATELESS_INSTR(concat_func, concat_instr);
  
  // block ops
  MAKE_STATELESS_INSTR(getelem_NP_func, getelem_NP_instr);
  MAKE_STATELESS_INSTR(setelem_NP_func, setelem_NP_instr);
  
  MAKE_STATELESS_INSTR(deref_func, deref_instr);
  MAKE_STATELESS_INSTR(setref_func, setref_instr);

  typedef shared_ptr<Instruction> InstructionRef;  
}

#endif

